package updater;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.generic.ClassGen;

import stringdecrypter.for651;
import transforms.AnimableTransforms;
import transforms.CanvasTransform;
import transforms.CharacterTransforms;
import transforms.ClientTransforms;
import transforms.ConnectionTransform;
import transforms.FileOnDiskTransform;
import transforms.FinderTransform;
import transforms.ItemDefLoaderTransforms;
import transforms.KeyboardTransforms;
import transforms.MouseTransforms;
import transforms.PlayerDefTransform;
import transforms.PlayerTransform;
import transforms.PlayerTransforms;
import transforms.RunScriptTransform;
import transforms.SignLinkTransform;
import transforms.supertransforms.MouseSuper;
import transforms.supertransforms.NPCSuper;

import com.rshacking.rhf.files.ClassContainer;
import com.rshacking.rhf.files.JarContainer;
import com.rshacking.rhf.updater.Updater;

public class RunUpdater {

	private final Updater updater;
	
	final static File archive = new File("archive");
	
	final static File cacheArchive = new File(archive, "cached");
	final static File unpackedLatestArchive = new File(cacheArchive, "latest_unpacked");
	final static File injectedArchive = new File(archive, "injected");
	final static File hooksArchive = new File(archive, "hooks");
	
	static File cached = null;

	public void runUpdater() {
		updater.execute();
	}

	String jartype = "-";
	String vers = "Unable to get version data";
	int cvers = -1;
	
	public void printLog() {
		System.out.println("Wolf's updater (" + jartype + ")");
		System.out.println(vers);
		System.out.println("For package \"" + updater.getPackage() + "\"");
		System.out.println("-------------------------\n");
		System.out.print(updater.log());
	}

	public void saveInjected(final String path) {
		updater.injectGetters();
		final File savin = new File(path);
		
		System.out.println("\nSaving injected jar to " + savin.getAbsolutePath());
		updater.getJar().save(savin);
	}
	
	public void saveXml(final File f) {
		updater.getHooks().exportXML().save(f);
		System.out.println("\nHooks saved to " + f.getAbsolutePath());
	}

	boolean cachelessCopy = false;
	
	public static void deleteDir(final File f) {
	    if (!f.isDirectory()) {
	        f.delete();
	        return;
	    }
	    for (final File ff : f.listFiles()) {
	        if (ff.isDirectory()) {
                deleteDir(ff);
            } else {
                ff.delete();
            }
	    }
	}
	
	public RunUpdater() {
		
		
		if (!archive.exists()) {
            archive.mkdir();
        }
		
		if (!cacheArchive.exists()) {
            cacheArchive.mkdir();
        }
		
		if (!injectedArchive.exists()) {
            injectedArchive.mkdir();
        }
		
		if (!hooksArchive.exists()) {
            hooksArchive.mkdir();
        }
		
		final File latest = new File(cacheArchive, "cached_latest.jar");
		if (latest.exists()) {
            cached = latest;
        } else {
            cached = new File(cacheArchive, "cachedrs");
        }
		
		JarContainer jar;
		
		URL rsu = null;
		try {
			rsu = new URL(
			"http://world1.runescape.com/runescape.jar");
		} catch (final MalformedURLException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		if (!cached.exists() || cachelessCopy) {
			jartype = "Reloaded&Cached jar";
			
			if (unpackedLatestArchive.exists()) {
                deleteDir(unpackedLatestArchive);
            }
			
			final long start = System.currentTimeMillis();
		    try {
		    	
		    	if (cached.exists()) {
                    cached.delete();
                }
		    	
				final ReadableByteChannel rbc = Channels.newChannel(rsu.openStream());
			    final FileOutputStream fos = new FileOutputStream(cached);
				fos.getChannel().transferFrom(rbc, 0, 1 << 24);
				
				unpackedLatestArchive.mkdir();
				
				final JarFile jf = new JarFile(cached);
				
				final Enumeration<JarEntry> entries = jf.entries();
				while (entries.hasMoreElements()) {
				    final JarEntry je = entries.nextElement();
				    
				    
				    if (je.getName().contains("META-INF")) {
                        continue;
                    }
				    
				    final File writeTo = new File(unpackedLatestArchive, je.getName());
				    
				    if (!writeTo.getParentFile().exists()) {
				        if (!writeTo.getParentFile().getParentFile().exists()) {
                            writeTo.getParentFile().getParentFile().mkdir(); // <-- stupid
                        }
                        writeTo.getParentFile().mkdir();
                    }
				    
				    final InputStream in = jf.getInputStream(je);
				    final OutputStream out = new FileOutputStream(writeTo);

				    // Transfer bytes from in to out
				    final byte[] buf = new byte[1024];
				    int len;
				    while ((len = in.read(buf)) > 0) {
				        out.write(buf, 0, len);
				    }
				    
				    in.close();
				    out.close();
				    
				}
				
				jf.close();
				
			} catch (final IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			jartype += " in " + (System.currentTimeMillis()-start) + " ms";
		}
		else {
			jartype = "Cached jar";
		}
		
		final HashMap<String, byte[]> clazzez = new HashMap<String, byte[]>();
		
		try {
            final JarFile jf = new JarFile(cached);

            jf.getManifest().clear();
            final Enumeration<JarEntry> jee = jf.entries();
            while (jee.hasMoreElements()) {
                final JarEntry je = jee.nextElement();
                if (je.getName().contains("META")) {
                    continue;
                }
                final InputStream jis = jf.getInputStream(je);
                byte[] data = new byte[0];
                byte[] oldData;
                final byte[] buffer = new byte[512];
                int size = 0;
                while ((size = jis.read(buffer)) > 0) {
                    oldData = data;
                    data = new byte[oldData.length + size];
                    System.arraycopy(oldData, 0, data, 0, oldData.length);
                    System.arraycopy(buffer, 0, data, oldData.length, size);
                }
                clazzez.put(je.getName(), data);
            }
            
            
            
        } catch (final IOException e) {
            e.printStackTrace();
        }
		
		jar = new JarContainer();
		
		
		System.out.println("Decrypting strings");
		for651.verbose = false;
		
		final HashMap<String, ClassContainer> finalClasses = new HashMap<String, ClassContainer>();
		
		final Iterator<Entry<String, byte[]>> scc = clazzez.entrySet().iterator();
		while (scc.hasNext()) {
		    
		    final Entry<String, byte[]> ecc = scc.next();
		    final ClassParser cp = new ClassParser(new ByteArrayInputStream(ecc.getValue()), ecc.getKey());

		    ClassGen cg = null;
            try {
                cg = new ClassGen(cp.parse());
            } catch (final ClassFormatException e) {
                e.printStackTrace();
            } catch (final IOException e) {
                e.printStackTrace();
            }
            
            for651.fixStringEncryption(cg);
            
            finalClasses.put(ecc.getKey(), new ClassContainer(cg.getJavaClass().getBytes()));
            
            scc.remove();
		}
		
		jar.putClassFiles(finalClasses);
		
		System.out.println("Strings succesfully decrypted");
		
		System.out.println("Running updater");
		updater = new Updater(jar);

		/*
	private String classFormat = " ^ %ocn% implements %p%.%cn%";
	private String fieldFormat = " * %fgn%() --> %s% %ot% %pn%.%ofn%";
	private String methodFormat = " ¤ %mn%() --> %s% %pn%.%omn%%od%";
		 */
		
		//updater.setFieldFormat("public %ot% %fgn%();");
		
		final ClassContainer cc = updater.getJar().getClassFiles().get("client.class");

        if (cc != null) {
        	final VersionVisitor vv = new VersionVisitor();
            cc.getASM(org.objectweb.asm.ClassReader.SKIP_FRAMES).accept(vv);
            
            cvers = vv.getVersion();
            vers = "Hooking RS Version #" + cvers;
        }
        
		updater.setPackage("com.waterwolf.wolfbot.internal");
		updater.addTransform(new ClientTransforms());
		//updater.addTransform(new InputTransforms());
		updater.addTransform(new SignLinkTransform());
		updater.addTransform(new FileOnDiskTransform());
		updater.addTransform(new ConnectionTransform());
		/*updater.addTransform(new InterfaceTransform());
		updater.addTransform(new NPCTransform());*/
		updater.addTransform(new PlayerTransform());
		updater.addTransform(new RunScriptTransform());
		
		updater.addTransform(new ItemDefLoaderTransforms());
		
		updater.addTransform(new PlayerDefTransform());
		//updater.addTransform(new ObjectDataTransform());
		updater.addTransform(new CharacterTransforms());
		updater.addTransform(new AnimableTransforms());
		updater.addTransform(new PlayerTransforms());
		updater.addTransform(new MouseTransforms());
		//updater.addTransform(new FinderTransform());
		updater.addTransform(new KeyboardTransforms());
		updater.addTransform(new CanvasTransform());
		
		//updater.addTransform(new GEItemTransforms()); // MUST BE AFTER RUNSCRIPT
		/*
		 * 
		 * updater.addTransform(new SignLinkTransform());
		 * updater.addTransform(new InterfaceTransform());
		 * updater.addTransform(new ClientTransforms());
		 * updater.addTransform(new NPCTransform());
		 */
		
		// Super transforms
		
		updater.addTransform(new NPCSuper());
		updater.addTransform(new MouseSuper());
	}

	public static void main(final String[] args) {
		final RunUpdater runUpdater = new RunUpdater();
		runUpdater.runUpdater();
		System.out.println("\n\n\n");
		runUpdater.printLog();
		
		if (cached != null) {
		    System.out.println("copying cached");
			try {
				copyFile(cached, new File(cacheArchive, "cached" + runUpdater.cvers + ".jar"));
			} catch (final IOException e) {
				e.printStackTrace();
			}
			cached.renameTo(new File(cacheArchive, "cached_latest.jar"));
		}
		final File injectTarget = new File(injectedArchive, "injected_client" + runUpdater.cvers + ".jar");
		runUpdater.saveInjected(injectTarget.getAbsolutePath());
		try {
			copyFile(injectTarget, new File(injectedArchive, "injected_client_latest.jar"));
		} catch (final IOException e) {
			e.printStackTrace();
		}
		final File hooksFile = new File(hooksArchive, "hooks_" + runUpdater.cvers + ".xml");
		runUpdater.saveXml(hooksFile);
		try {
            copyFile(hooksFile, new File(hooksArchive, "hooks_latest.xml"));
        } catch (final IOException e) {
            e.printStackTrace();
        }
	}
	
	public static void copyFile(final File sourceFile, final File destFile)
			throws IOException {
		if (!destFile.exists()) {
			destFile.createNewFile();
		}

		FileChannel source = null;
		FileChannel destination = null;
		try {
			source = new FileInputStream(sourceFile).getChannel();
			destination = new FileOutputStream(destFile).getChannel();
			destination.transferFrom(source, 0, source.size());
		} finally {
			if (source != null) {
				source.close();
			}
			if (destination != null) {
				destination.close();
			}
		}
	}

}